﻿using System.IO.Ports;
using System.Text;

namespace GCC.Container.Abstracts
{
    /// <summary>
    /// 抽象的串口通信客户端
    /// </summary>
    /// <typeparam name="T">通信数据类型</typeparam>
    /// <remarks>注意,由于串口得特殊性,不关析构了服务还是client,双方都将被联合析构,想要停止只能析构</remarks>
    public abstract class AbsCommCOMClient<T> : AbsCommClient, IDisposable
    {


        /// <summary>
        /// 是否已经销毁
        /// </summary>
        protected bool _disposed;
        /// <summary>
        /// 日志对象
        /// </summary>
        protected readonly AbsGCCLogger _logger;


        /// <summary>
        /// 头长度
        /// </summary>
        protected int _headSize = 4;

        /// <summary>
        /// 标识位长度
        /// </summary>
        protected int _flagSize = 2;

        /// <summary>
        /// 取消token
        /// </summary>
        protected readonly CancellationTokenSource _cancellToken = new();

        /// <summary>
        /// 串口对象
        /// </summary>
        protected SerialPort _serialPort;

        /// <summary>
        /// 是否是读行模式
        /// </summary>
        protected bool _isLineMode;

        /// <summary>
        /// 获取到一行
        /// </summary>
        public event Action<string>? GotLine;


        /// <summary>
        /// 解析到一个数据
        /// </summary>
        public event Action<T>? GotData;

        /// <summary>
        /// 销毁事件
        /// </summary>
        internal event Action? OnDisposing;



        /// <summary>
        /// 析构函数
        /// </summary>
        ~AbsCommCOMClient()
        {
            Dispose(false);
        }

        /// <summary>
        /// 销毁模型
        /// </summary>
        /// <param name="flag">是否是用户主动调用</param>
        protected virtual void Dispose(bool flag)
        {
            if (_disposed)
            {
                return;
            }
            _disposed = true;


            try
            {
                OnDisposing?.Invoke();
                _cancellToken.Cancel();
                _cancellToken.Dispose();
                if (_serialPort.IsOpen)
                {
                    _serialPort.Close();
                    _serialPort.Dispose();
                }
            }
            catch (Exception ex)
            {
                _logger.Error("AbsCommTcpClient dispose error.", exception: ex);
            }
        }

        /// <summary>
        /// 销毁模型
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }



        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger">日志模块实例</param>
        /// <param name="name">名称</param>
        public AbsCommCOMClient(string name, AbsGCCLogger logger)
        {
            ServerType = "COM";
            _logger = logger;
            Name = name;
            _serialPort = new SerialPort();
        }

        /// <summary>
        /// 绑定串口信息
        /// </summary>
        /// <param name="portName">端口名</param>
        /// <param name="baudRate">串行波特率</param>
        /// <param name="parity">奇偶校验检查协议。</param>
        /// <param name="dataBits">每个字节的标准数据位长度</param>
        /// <param name="stopBits">每个字节的标准停止位数</param>
        /// <param name="handshake">串行端口数据传输的握手协议</param>
        /// <param name="readTimeout">读取操作未完成时发生超时之前的毫秒数</param>
        /// <param name="writeTimeout">未完成时发生超时之前的毫秒数</param>
        /// <param name="isReadLineMode">是否是读行模式</param>
        /// <param name="encoding">如果是行模式,那么请给出编码</param>
        internal void BindTo(string portName,
            int? baudRate = null,
            Parity? parity = null,
            int? dataBits = null,
            StopBits? stopBits = null,
            Handshake? handshake = null,
            int? readTimeout = null,
            int? writeTimeout = null,
            bool? isReadLineMode = null,
            Encoding? encoding = null
            )
        {

            _serialPort.PortName = portName;
            if (baudRate is not null)
            {
                _serialPort.BaudRate = baudRate.Value;
            }
            if (parity is not null)
            {
                _serialPort.Parity = parity.Value;
            }
            if (dataBits is not null)
            {
                _serialPort.DataBits = dataBits.Value;
            }
            if (stopBits is not null)
            {
                _serialPort.StopBits = stopBits.Value;
            }
            if (handshake is not null)
            {
                _serialPort.Handshake = handshake.Value;
            }
            if (readTimeout is not null)
            {
                _serialPort.ReadTimeout = readTimeout.Value;
            }
            if (writeTimeout is not null)
            {
                _serialPort.WriteTimeout = writeTimeout.Value;
            }
            if (isReadLineMode is not null)
            {
                _isLineMode = isReadLineMode.Value;
            }
            if (encoding is not null)
            {
                _serialPort.Encoding = encoding;
            }
        }


        internal bool Start()
        {
            try
            {
                _serialPort.Open();
                _ = Task.Factory.StartNew(() =>
                {
                    byte[]? flagBuffer = null;
                    byte[]? headBuffer = null;
                    if (_flagSize != 0)
                    {
                        flagBuffer = new byte[_flagSize];
                    }
                    if (_headSize != 0)
                    {
                        headBuffer = new byte[_headSize];
                    }
                    var flagReadCount = 0;
                    var headReadCount = 0;
                    var bodyReadCount = 0;
                    while (!_cancellToken.Token.IsCancellationRequested)
                    {
                        bodyReadCount = headReadCount = flagReadCount = 0;
                        try
                        {
                            if (_isLineMode)
                            {
                                var data = _serialPort.ReadLine();
                                GotLine?.Invoke(data);
                            }
                            else if (flagBuffer is not null || headBuffer is not null)
                            {
                                var bodyLength = 0;
                                if (flagBuffer is not null)
                                {
                                    while (flagReadCount < _flagSize)
                                    {
                                        flagReadCount += _serialPort.Read(flagBuffer, flagReadCount, _flagSize - flagReadCount);
                                    }
                                    if (!CheckFlag(flagBuffer))
                                    {
                                        continue;
                                    }
                                }
                                if (headBuffer is not null)
                                {
                                    while (headReadCount < _headSize)
                                    {
                                        headReadCount += _serialPort.Read(headBuffer, headReadCount, _headSize - headReadCount);
                                    }
                                    if (!CheckHeader(flagBuffer, headBuffer, out bodyLength))
                                    {
                                        continue;
                                    }
                                }


                                var bodyDataBuffer = new byte[bodyLength];

                                while (bodyReadCount < bodyLength)
                                {
                                    bodyReadCount += _serialPort.Read(bodyDataBuffer, bodyReadCount, bodyLength - bodyReadCount);
                                }
                                GotData?.Invoke(HandleData(flagBuffer, headBuffer, bodyDataBuffer));
                            }
                        }
                        catch (Exception ex)
                        {
                            _logger.Error($"Com receive error.", exception: ex);
                            continue;
                        }
                    }
                }, _cancellToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
                return true;
            }
            catch (Exception ex)
            {
                _logger.Critical($"Start serial port server error.", exception: ex);
                return false;
            }
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="data">数据</param>
        /// <returns></returns>
        public bool Send(T data)
        {
            lock (this)
            {
                if (_isLineMode)
                {
                    return false;
                }
                try
                {
                    int totalLen = _headSize + _flagSize + GetBodyLengh(data);

                    var allDataBuffer = new byte[totalLen].AsMemory();
                    PutFlag(allDataBuffer[.._flagSize]);
                    PutHeader(allDataBuffer[.._flagSize], allDataBuffer[_flagSize..(_flagSize + _headSize)], data);
                    PutData(allDataBuffer[.._flagSize], allDataBuffer[_flagSize..(_flagSize + _headSize)], allDataBuffer[(_flagSize + _headSize)..], data);
                    _serialPort.Write(allDataBuffer.ToArray(), 0, totalLen);
                    return true;
                }
                catch (Exception ex)
                {
                    _logger.Error($"Com client {Name} send data error", exception: ex);
                    return false;
                }
            }
        }

        /// <summary>
        /// 发送一行文字
        /// </summary>
        /// <param name="data">数据</param>
        /// <returns></returns>
        public bool SendLine(string data)
        {
            lock (this)
            {
                if (!_isLineMode)
                {
                    return false;
                }
                try
                {
                    _serialPort.WriteLine(data);
                    return true;
                }
                catch (Exception ex)
                {
                    _logger.Error($"Com client {Name} SendLine error", exception: ex);
                    return false;
                }
            }
        }

        /// <summary>
        /// 获取数据的内容长度
        /// </summary>
        /// <param name="data"></param>
        protected abstract int GetBodyLengh(T data);


        /// <summary>
        /// 检查标志位
        /// </summary>
        /// <param name="flagBuffer">标志位buffer</param>
        /// <returns></returns>
        protected abstract bool CheckFlag(in Memory<byte> flagBuffer);

        /// <summary>
        /// 检查头
        /// </summary>
        /// <param name="flagBuffer">标识位缓冲区</param>
        /// <param name="headBuffer">头部缓冲</param>
        /// <param name="bodyLength">内容长度</param>
        /// <returns></returns>
        protected abstract bool CheckHeader(in Memory<byte>? flagBuffer, in Memory<byte> headBuffer, out int bodyLength);



        /// <summary>
        /// 处理数据
        /// </summary>
        /// <param name="flagBuffer">标识位buffer</param>
        /// <param name="headBuffer">头部buffer</param>
        /// <param name="bodyBuffer">数据buffer</param>
        protected abstract T HandleData(in Memory<byte>? flagBuffer, in Memory<byte> headBuffer, in Memory<byte> bodyBuffer);


        /// <summary>
        /// 检查标志位
        /// </summary>
        /// <param name="flagBuffer">标志位buffer</param>
        /// <returns></returns>
        protected abstract void PutFlag(Memory<byte> flagBuffer);

        /// <summary>
        /// 检查头
        /// </summary>
        /// <param name="flagBuffer">标识位缓冲区</param>
        /// <param name="headBuffer">头部缓冲</param> 
        /// <param name="data">数据</param>
        /// <returns></returns>
        protected abstract void PutHeader(in Memory<byte>? flagBuffer, Memory<byte> headBuffer, in T data);

        /// <summary>
        /// 处理数据
        /// </summary>
        /// <param name="flagBuffer">标识位buffer</param>
        /// <param name="headBuffer">头部buffer</param>
        /// <param name="bodyBuffer">数据buffer</param>
        /// <param name="data">数据</param>
        protected abstract void PutData(in Memory<byte>? flagBuffer, in Memory<byte> headBuffer, Memory<byte> bodyBuffer, in T data);

    }
}
